import uuid from 'uuid';
import genpass from 'generate-password';
import { Wechaty, Message, Contact, Room } from 'wechaty';
import PuppetPadchat from 'wechaty-puppet-padchat';

import qrTerm from 'qrcode-terminal';
import { makeLabelLogger } from '../util/logger';
import WebSocket from 'ws';
import { sleep } from '../util/tools';

let { llog, lerror } = makeLabelLogger('Agt');
export enum AgentStatus {
	NO_BOT = 1,
	HAS_BOT = 2,
	HAS_WXID = 3,
}
enum RunState {
	STOP = 1,
	STARTING = 2,
	START_FAILED = 3,
	STARTED = 4,
}
export class Agent {
	protected static allAgents = new Map<string, Agent>();
	protected static mapToken2Agent = new Map<string, Agent>();
	protected static mapToken2Waiter = new Map<string, boolean>();
	static getAllAgents() {
		return Agent.allAgents;
	}
	static async findOrCreateAgent(id?: string, options?: any) {
		if (options && options.puppet == 'wechaty-puppet-padchat') {
			console.log('check pad token...', options.token);
			if (Agent.mapToken2Waiter.has(options.token)) {
				console.log('already has waiter, reject!');
				return;
			}

			Agent.mapToken2Waiter.set(options.token, true);
			let oa = Agent.mapToken2Agent.get(options.token);
			if (oa) {
				console.log('found exist agent', oa.id);
				await oa.stop();
				Agent.deleteAgent(oa.id);
			}
			Agent.mapToken2Waiter.delete(options.token);
		}

		let agent: Agent;
		if (id) {
			agent = Agent.getAgent(id);
		}
		if (!agent) {
			agent = Agent.createAgent(id, options);
		}
		return agent;
	}
	static createAgent(id?, options?) {
		let agent = new Agent(id, options);
		Agent.allAgents.set(agent.id, agent);
		return agent;
	}
	static getAgent(id: string): Agent {
		return Agent.allAgents.get(id);
	}
	static deleteAgent(id: string) {
		let agent = Agent.allAgents.get(id);
		if (!agent) return;
		agent.stop();
		Agent.allAgents.delete(id);
	}
	//////////////////////////////////////////////////////////////////
	protected _id: string;
	protected _bot: Wechaty;
	protected _wxid: string;
	protected _qrCode: string;
	protected _options: Object;
	protected _runState: RunState;
	subCount: number;
	scanCount: number;

	get id() {
		return this._id;
	}
	get wxid() {
		return this._wxid;
	}
	get bot() {
		return this._bot;
	}
	get qrCode() {
		return this._qrCode;
	}
	get self() {
		return this._bot.userSelf();
	}

	get runState() {
		return this._runState;
	}

	get desc() {
		return {
			id: this.id,
			wxid: this.wxid,
			options: this._options,
			pcmsState: this.getPCMSocketState(),
			subCount: this.subCount,
		};
	}

	constructor(id?: string, options?) {
		this.scanCount = 0;
		this.subCount = 0;
		this._id =
			id ||
			genpass.generate({
				length: 8,
				numbers: true,
			});
		this._options = options;
		this._runState = RunState.STOP;

		let puppet = options ? options.puppet : undefined;
		if (puppet == 'wechaty-puppet-padchat') {
			let puppet = new PuppetPadchat({
				token: options.token,
			});
			this._bot = new Wechaty({
				puppet,
				profile: 'wxp/wxprofile-' + this.id,
			});
		} else {
			this._bot = new Wechaty({
				puppet,
				puppetOptions: options,
				profile: 'wxp/wxprofile-' + this.id,
			});
		}

		this.fixPadchat(puppet, options);
	}

	fixPadchat(puppet, options) {
		// 如果是pad协议，需要定时检查websocket连接是否关闭（被顶号)
		if (puppet == 'wechaty-puppet-padchat') {
			let self = this;

			Agent.mapToken2Agent.set(options.token, this);
			llog(`bind token ${options.token} to ${this.id}`);

			return;
			let listenClose = () => {
				let socket = this.getPCMSocket();
				if (!socket) {
					return setTimeout(listenClose, 1000);
				}
				 (socket as any).id = this.id;
				socket.on('close', (s, code, reason) => {
					llog('found pcm socket close, stop this agent', this.id);
					setTimeout(() => {
						llog('auto remove stopped pad', self.id);
						self.stop();
						// Agent.deleteAgent(self.id);
					}, 2000);
				});
			};
			setTimeout(listenClose, 1000);
		}
	}

	getPCMSocket() {
		let pm = (this.bot.puppet as any).padchatManager;
		if (!pm) return;
		let socket = pm.socket as WebSocket;
		return socket;
	}
	getPCMSocketState() {
		if (!this._bot) return 'nobot';
		let pm = (this.bot.puppet as any).padchatManager;
		if (!pm) return 'nopcm';
		let socket = pm.socket as WebSocket;
		if (!socket) return 'nosocket';
		return socket.readyState;
	}

	start() {
		if (this._runState != RunState.STOP) return;

		llog('starting bot', this.id);
		this._bot.start().catch(async e => {
			console.error('Bot start() fail:', e);
			this._runState = RunState.START_FAILED;
			await this._bot.stop();
		});
		this._runState = RunState.STARTING;
		this._bot
			.on('start', () => {
				llog('started', this.id);
				this._runState = RunState.STARTED;
			})
			.on('stop', () => {
				llog('stopped', this.id);
				this._runState = RunState.STOP;
			})
			.on('error', (error: Error) => {
				llog('error', this.id, error);
			})
			.on('scan', (qrCode: string) => {
				this._qrCode = qrCode;
			})
			.on('message', (msg: Message) => {
				let text = msg.text();
				llog('msg', msg.type(), msg.from().name(), msg.text());
			})
			.on('login', (user: Contact) => {
				llog('login', user.id);
				this._wxid = user.id;
			})
			.on('logout', (user: Contact) => {
				llog('logout', user.id);
				this._wxid = undefined;
			});
	}

	async stop() {
		let k = 100;
		while (this._runState == RunState.STARTING && k >= 0) {
			llog('stopping a launching agent, wait for complete', this.id);
			--k;
			await sleep(100);
		}
		await this._bot.stop();
	}

	async getContacts() {
		let contacts = await this._bot.Contact.findAll();
		llog('get contacts', this.id, this.wxid, contacts.length);
		return contacts.map((v: Contact) => {
			return {
				id: v.id,
				type: v.type(),
				name: v.name(),
			};
		});
	}
	async getRooms() {
		try {
			let zooms = await this._bot.Room.findAll();
			llog('getRooms', this.id, this.wxid, zooms.length);
			let topics = await Promise.all(zooms.map((v: Room) => v.topic()));
			llog('room topic', topics);
			return zooms.map((v, k) => {
				v.on('topic', (oldTopic: string, newTopic: string, changer: Contact) => {
					llog('change title', oldTopic, newTopic);
					return;
				});
				return {
					id: v.id,
					name: topics[k],
				};
			});
		} catch (e) {
			llog('getRooms error', e);
			return [];
		}
	}

	async sendMessage(contactId, text) {
		try {
			let contact = await this._bot.Contact.load(contactId);
			await contact.say(text);
		} catch (error) {
			llog('sendMessage fail', contactId, text);
		}
	}

	async processCommand(cmd: string) {
		switch (cmd) {
			case '2':
				{
					try {
						let socket = (this.bot.puppet as any).padchatManager.socket as WebSocket;
						console.log('bot', socket.readyState);
						return true;
					} catch (error) {
						return false;
					}
				}
				break;
			case '3':
				{
					llog('list all agents', Agent.getAllAgents().keys());
					Agent.getAllAgents().forEach((agent, id, map) => {
						console.log('agent', agent.id, agent.wxid);
					});
				}
				break;

			default:
				break;
		}
		return;
	}
}
